Skip to content
Built 26/04/17 09:39commit 8de3d61

中文 | English

在过去五个月里,我们团队一直在进行一个实验:在完全 0 行人工手写代码的前提下,构建并交付一个软件产品的内部 beta 版。

这个产品已经有内部日活用户,也有外部 alpha 测试者。它会发布、会部署、会出错,也会被修好。不同之处在于:从应用逻辑、测试、CI 配置、文档、可观测性到内部工具,每一行代码都由 Codex 编写。我们估算,这个产品的构建速度大约是纯手写代码的 10 倍。

人类负责把方向盘握稳,agent 负责执行。

我们是有意选了这个约束,因为如果想把工程效率提升几个数量级,就必须逼自己构建那些真正必要的东西。当时我们只有几周时间,却最终交付了一个大约百万行代码的产品。要做到这一点,我们必须搞清楚:当软件工程团队的主要工作不再是亲手写代码,而是设计环境、表达意图、构建反馈回路,以便让 Codex agent 能可靠地工作时,工程实践到底会发生什么变化。

这篇文章讲的是我们在用 agent 团队构建一个全新产品时学到的东西:什么会坏掉,什么会复利,以及如何最大化我们唯一真正稀缺的资源:人类的时间与注意力。

我们从一个空的 git 仓库开始

这个空仓库的第一条提交出现在 2025 年 8 月底。

最初的脚手架,包括仓库结构、CI 配置、格式化规则、包管理器设置和应用框架,都是由 Codex CLI 配合 GPT‑5,并参考一小组现成模板生成的。甚至那个最初用来指导 agent 如何在仓库中工作的 AGENTS.md,也是 Codex 写出来的。

一开始并没有任何预先存在的人类手写代码来“锚定”系统。这个仓库从一开始就是被 agent 塑形的。

五个月后,这个仓库已经包含了大约百万行代码,覆盖应用逻辑、基础设施、工具链、文档和内部开发工具。在这段期间里,一个只有三名工程师的小团队驱动 Codex 打开并合并了约 1500 个 pull request。折算下来,相当于每位工程师平均每天 3.5 个 PR,而且随着团队扩展到 7 人,这个吞吐量居然还在增加。更重要的是,这不是为了“产出而产出”:这个产品已经被数百名内部用户使用,其中不乏每日高频使用者。

在整个开发过程中,人类从未直接提交任何手写代码。这也逐渐演变成团队的核心原则:不手写代码。

重新定义工程师的角色

人类不再亲手编码,并没有让工程工作消失,反而引入了另一种工程劳动:它更聚焦于系统、脚手架和杠杆。

一开始的进展其实比预期更慢,不是因为 Codex 不行,而是因为环境定义得还不够完整。agent 缺少足够的工具、抽象和内部结构,无法对高层目标做出有效推进。于是我们工程团队的主要工作,逐渐转成了“让 agent 有能力做出有用工作”。

在实践中,这意味着一种纵深式工作方式:把更大的目标拆成更小的构件(设计、编码、评审、测试等),先让 agent 把这些构件搭起来,再利用这些构件解锁更复杂的任务。每当哪里失败,解决方式几乎从来不是“再试一次、再努力一点”。因为唯一能真正推进工作的方式就是让 Codex 自己来做,所以人类工程师总是在问:“到底缺了什么能力?我们要怎样把它同时变得可读而且可执行?”

人类和系统的交互几乎完全通过 prompt 完成:工程师描述一个任务,运行 agent,让它打开 PR。为了把一个 PR 推到完成,我们会指示 Codex 在本地审查自己的改动、在本地和云端继续请求更具体的 agent review、响应人类或 agent 的反馈,并持续循环,直到所有 agent reviewer 都满意为止(本质上就是 Ralph Wiggum Loop)。Codex 直接使用我们常规的开发工具,比如 gh、本地脚本和仓库内技能,而不是让人类在 CLI 里手动复制粘贴上下文。

人类当然也可以 review PR,但并不是必须的。随着时间推移,我们逐渐把几乎所有 review 工作都推向了 agent-to-agent。

提高应用的可读性

随着代码吞吐增加,我们真正的瓶颈变成了人类 QA 的能力。既然真正固定的稀缺资源是人类时间与注意力,我们就开始持续为 agent 增加能力:让应用 UI、日志、指标这些东西本身都能被 Codex 直接读取和理解。

例如,我们让应用可以按 git worktree 启动,这样 Codex 就能为每次改动拉起一个独立实例。我们还把 Chrome DevTools Protocol 接进 agent 运行时,并围绕 DOM 快照、截图和导航写了一整套 skill。这样 Codex 就能直接重现 bug、验证修复,并对 UI 行为进行推理。

我们对可观测性工具也做了同样的事情。日志、指标和 traces 都通过一套本地可观测性栈暴露给 Codex,而这个栈对每个 worktree 都是短暂隔离的。Codex 工作在应用的完全隔离版本上,包括它自己的日志和指标,而这些东西会在任务完成后被销毁。Agent 可以用 LogQL 查日志,用 PromQL 查指标。有了这套上下文,像 “确保服务启动在 800ms 内完成” 或 “这四条关键用户路径里没有任何 span 超过 2 秒” 这样的 prompt,才真正变得可操作。

我们经常看到一次 Codex 运行围绕单个任务工作六小时以上,而且很多时候正好发生在人类睡觉的时候。

我们把仓库知识变成 system of record

上下文管理是让 agent 在大型复杂任务中真正有效的最大挑战之一。我们最早学到的经验非常简单:给 Codex 一张地图,而不是一本 1000 页说明书。

我们试过“一份巨大的 AGENTS.md” 方案。它以一种很可预测的方式失败了:

  • 上下文是稀缺资源。 一个巨大的说明文件会把任务本身、代码本身以及真正相关的文档挤出上下文窗口,结果就是 agent 要么漏掉关键约束,要么开始围绕错误目标优化。
  • 太多指导就等于没有指导。 当所有东西都被标记成 “重要”,就没有任何东西真的重要。Agent 最后会变成就地 pattern match,而不是有意识地导航。
  • 它会瞬间腐烂。 一个单体手册很快就变成了过时规则的坟场。Agent 无法判断哪些还有效,人类也懒得维护,这份文件最后会默默变成一个有害诱饵。
  • 它很难验证。 一整个大块文本不方便做机械检查(覆盖率、新鲜度、owner、交叉链接等),所以漂移几乎不可避免。

所以我们不再把 AGENTS.md 当百科全书,而是把它当成目录页

仓库的知识库主体存放在结构化的 docs/ 目录里,并被当作 system of record。一个简短的 AGENTS.md(大约 100 行)会直接注入上下文,它的主要职责是做地图,把 agent 指到更深层的事实来源。

设计文档会被编目和索引,其中包含验证状态,以及定义 agent-first 运行原则的一组核心信念。架构文档提供了顶层的域图与包分层说明。质量文档则对各产品域和架构层做打分,并随着时间跟踪缺口。

计划也被视为一等产物。简单变更使用轻量临时计划,复杂工作则会被写入 execution plans,并把进度与决策日志检入仓库。活跃计划、已完成计划和已知技术债都被版本化并放在一起,这使得 agent 不必依赖外部上下文也能运转。

这实现了一种渐进式披露:agent 从小而稳定的入口开始,再被教会下一步该去哪里看,而不是一开始就被信息淹没。

而且我们是机械性地强制执行这一点。专门的 linter 和 CI job 会验证知识库是否最新、是否交叉链接完善、结构是否正确。我们还会有一个周期性的 “doc-gardening” agent,扫描那些已经过时或不再反映真实代码行为的文档,并自动打开修复 PR。

Agent legibility 才是目标

随着代码库演化,Codex 理解设计决策的框架也必须一起演化。

因为这个仓库完全由 agent 生成,所以它首先是为 Codex 的可读性 优化的。就像团队会努力让新员工更容易理解代码一样,我们的人类工程师的目标,是让 agent 能仅凭仓库本身就理解完整的业务领域。

从 agent 的视角看,任何它在运行中无法就地访问到的东西,基本都等于不存在。存在于 Google Docs、聊天记录或人脑中的知识,对系统来说都不可见。只有仓库内、版本化的产物(代码、markdown、schema、可执行计划等)才是它真正能看到的东西。

我们逐渐意识到,必须把越来越多的上下文推入 repo。本来只存在于 Slack 里的架构讨论,如果 agent 无法发现,那它就像一个三个月后刚入职的新员工一样,对这件事一无所知。

给 Codex 更多上下文,并不意味着把乱七八糟的信息一股脑塞给它,而是要把正确的信息组织并暴露出来,让 agent 可以对它们推理。就像你会给新同事讲产品原则、工程规范和团队文化一样,把这些东西提供给 agent,会明显提升它输出的对齐度。

这个框架也帮助我们看清了很多权衡。我们更偏向那些能在 repo 内完全被理解和推理的依赖与抽象。通常被称为 “boring” 的技术,对 agent 来说往往更容易建模,因为它们组合性更好、API 更稳定,而且在训练数据里出现得更多。某些情况下,直接让 agent 自己重写一小部分功能,反而比和一个黑盒上游库博弈更便宜。例如,我们没有引入一个通用的 p-limit 风格包,而是自己实现了一个带并发限制的 map helper,因为它和我们的 OpenTelemetry 埋点深度集成、测试覆盖 100%,而且行为完全符合我们的运行时预期。

把更多系统部分变成 agent 可检查、可验证、可修改的形态,不只是提升 Codex 的杠杆,也会提升其他一起在代码库里工作的 agent(例如 Aardvark)的杠杆。

强制架构与品味

光靠文档并不能让一个完全由 agent 生成的代码库保持一致。通过强制执行不变量,而不是事无巨细地限制实现方式,我们才能让 agent 快速交付,又不至于破坏地基。 例如,我们要求 Codex 在边界处解析数据 shape,但不规定具体怎么做(模型似乎偏爱 Zod,不过不是我们指定的)。

Agent 在 边界严格、结构可预测 的环境里工作效果最好,所以我们围绕一个刚性的架构模型来构建应用。每个业务域都被拆成固定的一组层,依赖方向被严格校验,只允许有限的边。跨切关注点(auth、connectors、telemetry、feature flags)只能通过一个显式接口进入:Providers。其他任何方式都不允许,并且由定制 linter 和结构测试机械执行。

这类架构在传统团队里,你往往会等到几百名工程师时才开始认真考虑;但在 coding agents 的环境里,它反而成了早期前提,因为正是这些约束让高速开发成为可能,而不会迅速衰败或出现架构漂移。

在实践中,我们用自定义 linter、结构测试和一小套 “taste invariants” 来执行这些规则。比如我们会静态强制结构化日志、schema 和类型的命名约定、文件大小限制,以及平台可靠性要求等。因为 linter 是定制的,所以我们可以把错误消息写成同时带修复指导的形式,让 agent 直接在上下文中学会如何恢复。

在以人为中心的工作流里,这些规则可能会显得刻板甚至束缚;但对 agent 而言,它们是乘数:一旦编码进去,就能在所有地方同时生效。

同时,我们也很明确地区分了“哪里需要强约束、哪里不需要”。这更像是在领导一个大型平台工程组织:边界由中心统一强制,边界之内允许局部自治。你非常在意边界、正确性和可复现性;而在这些约束内部,则允许团队——或者 agent——有相当大的表达自由。

最终生成的代码不一定总符合人类工程师的风格偏好,而这没有关系。只要输出是正确的、可维护的,而且对未来 agent 来说依然可读,那它就达标了。

人类的品味会通过 review 评论、重构 PR 和用户可见 bug 持续反馈回系统中。这些反馈会被提升为文档更新或直接编码进工具。当文档不够时,我们就把规则进一步升级为代码。

吞吐改变了合并哲学

随着 Codex 吞吐持续增加,很多传统工程规范反而开始显得不合时宜。

这个仓库几乎没有阻塞式 merge gate。PR 生命周期很短。测试抖动通常会通过后续运行来修,而不是无限期阻塞进度。在 agent 吞吐远超人类注意力的系统里,修正很便宜,而等待很贵。

放在低吞吐环境里,这种做法当然不负责任;但在这里,它往往是正确的权衡。

“agent-generated” 到底意味着什么

当我们说这个代码库是由 Codex agents 生成的,我们的意思是:代码库里的几乎一切,都是 agent 产出的。

人类始终在环,但工作抽象层次已经和过去完全不同。我们负责设定优先级、把用户反馈翻译成验收标准、以及验证结果。当 agent 挣扎时,我们把这看作一个信号:识别到底缺少什么——工具、护栏、文档——再把它反馈回仓库,而且始终通过让 Codex 自己来写修复。

Agent 会直接使用我们的标准开发工具。它们会拉取 review 反馈、在线回复、推送更新,并且经常自己 squash 并 merge PR。

持续提高自治水平

随着测试、验证、评审、反馈处理和恢复机制越来越多地被直接编码进系统,仓库最近跨过了一个有意义的门槛:Codex 已经可以端到端驱动一个新功能。

只要给一个 prompt,agent 现在就能:

  • 验证代码库当前状态
  • 重现已报告的 bug
  • 录一段视频展示 bug
  • 实现修复
  • 驱动应用验证修复
  • 再录一段视频展示修复成功
  • 打开 pull request
  • 响应 agent 和人类的反馈
  • 发现并修复构建失败
  • 仅在真正需要判断时才升级给人类
  • 合并改动

这种行为高度依赖本仓库当前的结构和工具链,不应假设在没有类似投入的仓库里也能直接复用——至少现在还不能。

熵与垃圾回收

完全 agent 自主同样会带来新的问题。 Codex 会复制仓库中已经存在的模式——哪怕这些模式本身并不均匀,甚至并不理想。随着时间推移,这几乎注定会导致漂移。

最初,人类手动处理这些问题。我们团队以前每周五都会花掉整整一天(大约 20% 的时间)来清理 “AI slop”。很显然,这种做法不可能扩展。

于是我们开始把所谓的 “golden principles” 直接编码进仓库,并构建了一个周期性 cleanup 流程。这些原则是机械化、带有明确偏好的规则,用来让代码库对未来的 agent 运行始终保持可读和一致。比如:(1)我们更偏向共享 utility package,而不是到处手搓 helper,这样不变量就能集中管理;(2)我们不喜欢 “YOLO-style” 地探测数据结构,而是要求边界处做验证,或者依赖类型化 SDK,这样 agent 就不会误把猜出来的 shape 当作事实。我们有一组后台 Codex 任务会定期扫描偏离、更新质量评分,并打开有针对性的重构 PR。大多数这类 PR 人类 review 一分钟以内就能搞定,甚至可以自动合并。

这其实就是一种垃圾回收。技术债像高利贷:几乎总是更值得持续、小步地偿还,而不是任由它滚大、最后再集中痛苦偿还。人类品味被捕捉一次,然后持续在每一行代码上执行。这也让我们可以按天而不是按周去发现和纠正坏模式,防止它们在代码库里蔓延。

我们仍在学习什么

到目前为止,这套策略在 OpenAI 的内部上线和采用阶段运行得很好。为真实用户构建真实产品,迫使我们的投入必须落在现实问题上,也帮助我们把注意力放在长期可维护性上。

但我们仍然不知道:在一个完全由 agent 生成的系统里,架构一致性会如何在多年尺度上演化。我们也仍在摸索人类判断在哪些地方最有杠杆,以及如何把这种判断编码成能持续复利的系统。随着模型变得越来越强,我们同样不知道这整套系统会如何变化。

已经很清楚的一点是:构建软件仍然需要纪律,只不过这种纪律越来越体现在脚手架,而不是具体代码本身。保持代码库一致性的工具、抽象和反馈回路,正变得越来越重要。

我们最难的问题,现在集中在设计环境、反馈回路和控制系统上,目标是帮助 agent 完成我们的真正目标:在大规模上构建并维护复杂而可靠的软件。

随着像 Codex 这样的 agent 承担软件生命周期中越来越大的部分,这些问题只会变得更重要。我们希望分享这些早期经验,能帮助你判断应该把精力投入在哪里,这样你就能真正“把东西做出来”。

作者

Ryan Lopopolo

致谢

特别感谢 Victor Zhu 和 Zach Brock 对本文的贡献,也感谢整个构建这个新产品的团队。